package ru.yelbota.flex.as3jobs.classes {

import flash.events.ProgressEvent;
import flash.net.Socket;
import flash.utils.ByteArray;
import flash.utils.IDataInput;

import ru.yelbota.flex.as3jobs.Job;
import ru.yelbota.flex.as3jobs.as3_jobs;
import ru.yelbota.flex.as3jobs.exceptions.JobSimpleResourceException;
import ru.yelbota.flex.as3jobs.exceptions.JobWaitEventException;

use namespace as3_jobs;

/**
 * Synchronous wraper for using in as3-jobs. Provides synchronous interface for
 * reading from socket. Note that some methods like readObject cant be wrapped.
 * // TODO need deep testing
 */
public class SynchronousSocketReader implements IDataInput {

    private static const unsupported:String = "This method is not supported in synchronous mode";

    /**
     * Wrapped socket
     */
    private var source:Socket;

    /**
     * Do not use constructor in job code.
     * @param source
     */
    public function SynchronousSocketReader(source:Socket) {
        this.source = source;
    }

    public static function createInJob(source:Socket):SynchronousSocketReader {

        const name:String = "SynchronousDataInput" + Job.getCurrentCallIndex();
        const resource:Object = Job.getResourceOrElse(Job.getCurrentJob(), name, function():Object {
            return new SynchronousSocketReader(source);
        });

        return resource as SynchronousSocketReader;
    }

    private function read(name:String, length:uint, f:Function):* {

        Job.incrementCallIndex();

        const rname:String = "SynchronousDataInput$" + name + "$" + Job.getCurrentCallIndex();
        const resource:Object = Job.getResource(Job.getCurrentJob(), rname);

        if (resource == null || resource is SynchronousSocketReader) {
            if (super.bytesAvailable >= length) {
                throw new JobSimpleResourceException(f(), rname)
            }
            else {
                throw new JobWaitEventException(ProgressEvent.SOCKET_DATA, source, rname);
            }
        }

        return resource;
    }

    public function readBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0):void {

        const resource:Object = read("readBytes", length ? length : bytesAvailable, function():Object {
            source.readBytes(bytes, offset, length);
            const savedResource:ByteArray = new ByteArray();
            bytes.readBytes(savedResource);
            return savedResource;
        });

        if (resource != null) {
            ByteArray(resource).readBytes(bytes, offset, length);
        }
    }

    public function readBoolean():Boolean {
        return read("readBoolean", 1, function():Object {
            return source.readBoolean();
        });
    }

    public function readByte():int {
        return read("readByte", 1, function():Object {
            return source.readByte();
        });
    }

    public function readUnsignedByte():uint {
        return read("readUnsignedByte", 1, function():Object {
            return source.readUnsignedByte();
        });
    }

    public function readShort():int {
        return read("readShort", 2, function():Object {
            return source.readShort();
        });
    }

    public function readUnsignedShort():uint {
        return read("readUnsignedShort", 2, function():Object {
            return source.readUnsignedShort();
        });
    }

    public function readInt():int {
        return read("readInt", 4, function():Object {
            return source.readInt();
        });
    }

    public function readUnsignedInt():uint {
        return read("readUnsignedInt", 4, function():Object {
            return source.readUnsignedInt();
        });
    }

    public function readFloat():Number {
        return read("readFloat", 8, function():Object {
            return source.readFloat();
        });
    }

    public function readDouble():Number {
        return read("readDouble", 8, function():Object {
            return source.readDouble();
        });
    }

    public function readMultiByte(length:uint, charSet:String):String {
        return read("readMultiByte", length, function():Object {
            return source.readMultiByte(length, charSet);
        });
    }

    public function readUTF():String {
        return readUTFBytes(readUnsignedShort());
    }

    public function readUTFBytes(length:uint):String {
        return read("readUTFBytes", length, function():Object {
            return source.readUTFBytes(length);
        });
    }

    public function get bytesAvailable():uint {

        Job.incrementCallIndex();
        const name:String = "SynchronousDataInput$bytesAvailable$" + Job.getCurrentCallIndex();
        const resource:Object = Job.getResourceOrElse(Job.getCurrentJob(), name, function():Object {
            return source.bytesAvailable;
        });

        return resource as uint;
    }

    public function readObject():* {
        throw new Error(unsupported);
    }

    public function get objectEncoding():uint {
        return source.objectEncoding;
    }

    public function set objectEncoding(version:uint):void {
        throw new Error(unsupported);
    }

    public function get endian():String {
        return source.endian;
    }

    public function set endian(type:String):void {
        throw new Error(unsupported);
    }
}

}
